package com.hotel.component;

import com.hotel.exception.FormatException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Controller基类
 */
public abstract class BaseController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        int idx = uri.lastIndexOf("/");
        //{1}获取方法名 (动作名)。
        String mtdName = uri.substring(idx + 1, uri.length());
        String respRet = null;
        try {
            //{2}调用分发方法来分发请求。
            respRet = dispatcher(mtdName, req, resp);
            //{3}如果没有返回值, 报出 404 错误。
            if (respRet == null) {
                //下一次课还要整改。
                throw new NoSuchMethodException("返回值为空。");
            } else if ("no_action".equals(respRet)) {
                return;
            }
            //{4}调用 parseResult方法解析结果
            parseResult(respRet, req, resp);
        } catch (IOException | ServletException e) { //异常下一次课还要整改..
            //[开发阶段-程序员造成]
            e.printStackTrace();
            resp.sendError(500, "服务器内部发生错误。");
        } catch (NoSuchMethodException e) {      //404 [客户/程序员]
            resp.sendError(404, e.getMessage());
        } catch (InvocationTargetException e) {  //程序员 [运行阶段]
            //{1}运行时异常, 空指针, 下标越界, 数据库等。
            Throwable cause = e.getCause();  //异常发生的源头
            resp.sendError(500, cause.toString());
        } catch (FormatException e) {     //程序员 [开发阶段]
            e.printStackTrace();
            resp.sendError(500, e.getMessage());
        }
    }

    //三种常见的导航指令。
    //{1}redirect:/User/index
    //{2}forward:/User/HelloServlet
    //{3}data:Hello_andy
    private void parseResult(String respRet, HttpServletRequest req, HttpServletResponse resp)
            throws FormatException, IOException, ServletException, NoSuchMethodException, InvocationTargetException {
        //{1}解析导航指令, 解析出错抛出异常。
        String regex = "([a-z]+):(.+)";
        Pattern pat = Pattern.compile(regex);
        Matcher mat = pat.matcher(respRet);
        if (!mat.matches()) {
            throw new FormatException(respRet + "格式错误。");
        }
        String action = mat.group(1);
        String content = mat.group(2);
        //{2}调用 respDispatcher 方法
        respDispatcher(action, content, req, resp);
    }

    //respDispatcher 方法
    private void respDispatcher(String action, String content, HttpServletRequest req, HttpServletResponse resp)
            throws NoSuchMethodException, InvocationTargetException {
        //{1}拿到当前对象的字节码(xxxController)
        Class clazz = this.getClass();
        //{2}拿到父类的字节码(BaseController)
        Class baseClz = clazz.getSuperclass();
        //{3}根据 action 获取父类的某个方法
        Method method = baseClz.getDeclaredMethod(action,
                String.class, HttpServletRequest.class,
                HttpServletResponse.class);
        method.setAccessible(true);
        try {
            //{4}调用方法, 传入 this (UserController)
            //调用以下三个方法: redirect, forward, data
            method.invoke(this, content, req, resp);
        } catch (IllegalAccessException
                | IllegalArgumentException e) {
            //这两个异常, 我已控死, 不让其发生。
            e.printStackTrace();
        }
    }

    //以下三个方法是给上面的  respDispatcher 来调用。。
    // 重定向
    private void redirect(String content, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("{BaseController}执行重定向:" + content);
        //{1}获取应用程序上下文根映射地址
        String ctxPath = req.getContextPath();
        resp.sendRedirect(ctxPath + content);
    }

    // 转发
    private void forward(String content, HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("{BaseController}执行内部转发:" + content);
        req.getRequestDispatcher(content).forward(req, resp);
    }

    // 传数据
    private void data(String content, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write(content);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //{ps}无论从哪里进来, 统统引流到 doGet
        doGet(req, resp);
    }

    private String dispatcher(String mtdName, HttpServletRequest req, HttpServletResponse resp)
            throws NoSuchMethodException, InvocationTargetException, UnsupportedEncodingException {
        // 设置编码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        //{1}获取当前对象的字节码 ---> UserController.class
        //                       AdminController.class
        Class clz = this.getClass();
        //{2}根据 mtdName 获取方法
        // 为什么是这两种类型, 子类固定了使用这个两种类型。
        Method method = null;
        try {
            method = clz.getDeclaredMethod(mtdName,
                    HttpServletRequest.class,
                    HttpServletResponse.class);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodException(
                    "找不到该资源:" + req.getRequestURI());
        }
        //{3}打开访问控制权限
        method.setAccessible(true);
        String ret = null;
        try {
            //{3}传入参数并调用方法
            ret = (String) method.invoke(this, req, resp);
        } catch (IllegalAccessException |
                IllegalArgumentException e) {
            //{ps}这两个异常控死了, 不会发生。
            e.printStackTrace();
        }
        return ret;
    }


    //------------------------------------------------------

    /**
     * 用来做用户搜索
     * //{1}获取关键字: username, sex, ...
     * //   (排除 ran)
     * //{2}获取 page, limit [分页参数]
     * //{3}处理 page, limit
     *
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    protected Map<String, Object> getKeyWords(HttpServletRequest req) {
        //{1}获取请求参数..
        Map<String, String[]> parMap = req.getParameterMap();
        //{2}创建一个新的参数容器
        Map<String, Object> newMap = new HashMap();
        //{3}迭代每一组
        Set<String> keySet = parMap.keySet();
        for (String key : keySet) {
            if ("ran".equals(key)) {
                continue;  //ran 是防止浏览器不提交数据。
            }
            String[] vals = parMap.get(key);
            //{4}存入数据到新的 map (为什么是 0 ?) 现在做一个关键字只有一个值，不考虑多值
            newMap.put(key, vals[0]);
        }
        //{5}取 page(页码), 没有的话默认为 1
        Integer page = fetchPara(newMap, "page", 1);
        //{6}取 limit (页大小), 没有的话默认为 10
        Integer limit = fetchPara(newMap, "limit", 10);
        newMap.put("page", page);
        newMap.put("limit", limit);
        return newMap;
    }

    /**
     * 从newMap中提取参数
     *
     * @return java.lang.Integer
     */
    private Integer fetchPara(Map<String, Object> newMap, String key, int defVal) {
        String val = (String) newMap.get(key);
        if (val == null) {   //为空
            return defVal; //返回默认值。。
        }
        //{ps}你是不是一个数字。
        // 用于检测字符串是否匹配给定的正则表达式
        boolean isNum = val.matches("[0-9]+");
        if (!isNum) {       //格式不对
            return defVal;  //返回默认值。。
        }
        return Integer.valueOf(val);
    }

    protected void download(String path, String file, HttpServletResponse resp)
            throws ServletException, IOException {
        InputStream is = new FileInputStream(path + file);
        //{1}对文件名进行url编码操作。
        file = URLEncoder.encode(file, "UTF-8");
        int size = is.available();
        // {1}设置响应头正文长度
        resp.setContentLength(size);
        //{2}内容类型:应用程序下载资料
        resp.setContentType("application/x-msdownload");
        //{3}指定我这个是一个附件，名字是AA.pdf
        resp.addHeader("Content-Disposition", "attachment;filename=\"" + file + "\"");
        byte[] buff = new byte[8192];
        int count = 0;
        OutputStream os = resp.getOutputStream();
        while (is.available() > 0) {
            count = is.read(buff);
            os.write(buff, 0, count);
        }
        is.close();
        os.close();
    }

    protected Float parseFloat(String value) {
        return (value == null) ? 0 : Float.valueOf(value);
    }

    protected Double parseDouble(String value) {
        return (value == null) ? 0 : Double.valueOf(value);
    }

    protected Integer parseInteger(String value) {
        return (value == null) ? 0 : Integer.valueOf(value);
    }

}